雖然每種程式語言提供的變數類別大同小異,但如果仔細檢視,就會發現實作邏輯上會有些微的不同。掌握這些不同之處,正是學習新語言的「眉角」所在。接下來介紹go所用到的一些基礎類別。
任何語言一定會有字串,用來儲存或表示人類可讀的資料,例如姓名或地址。go預設使用utf8編碼,支援各國語言。以下是簡單的例子:
s := "hello, world"
字串有內建一些方法,例如len
與[]
:
fmt.Println(len(s)) // "12"
fmt.Println(s[0], s[7]) // "104 119" ('h' and 'w')
len
會返回字串長度;[]
則會返回字節的值。如果搭配:
可以取得新的字串,:
左右各搭配一個字節索引,但都可以省略,省略時則取到開頭或結尾。如果左右都省略,則返回字串本身。範例如下:
fmt.Println(s[0:5]) // "hello"
fmt.Println(s[:5]) // "hello"
fmt.Println(s[7:]) // "world"
fmt.Println(s[:]) // "hello, world"
每種語言也一定都會有整數。go實際上提供了四種整數型態:int8
、int16
、int32
、int64
,分別對應8、16、32、64bit有符號整數;與之相對的還有uint8
、uint16
、uint32
、uint64
無符號整數。
但實務上最常使用的就是int
與uint
,可能對應32或64bit,編譯時會自己決定,沒有辦法改變預設。如果你嘗試將不同位元的整數一起運算,會出現編譯錯誤:
var apples int32 = 1
var oranges int16 = 2
var sum int = apples + oranges // compile error
這種情況下需要先轉換才可以(所以沒事最好都使用int
):
var sum = int(apples) + int(oranges)
雖然我很想說每一種語言一定都有布林值,但我記得我之前學過一種語言,他的布林值其實是symbol的特例(:true
與:false
)。不過go沒有symbol這個類別,九成九的語言也確實都有布林值。
go似乎沒有內建的布林轉換,如果需要的話,可以參考下面的例子:
// btoi returns 1 if b is true and 0 if false.
func btoi(b bool) int {
if b {
return 1
}
return 0
}
或是將布林值轉換為整數:
// itob reports whether i is non-zero.
func itob(i int) bool { return i != 0 }
雖然還有浮點數與復數,但我就省略了,有興趣的朋友可以參考連結。接下來介紹陣列與雜湊:
go的陣列有兩種(斯斯有兩種?!),分別是Array與Slice,後者的名稱在其他語言中比較少見,不過性質差不多。
Array在go的定義下長度是固定的,而且類別需要相同,與Ruby相比喪失了許多的自由度(整個語言到處都給我這種感覺)。但利弊其實是一體的兩面,自我限制的同時,也增加了結構的嚴謹,使代碼不容易產生預料外的錯誤。(但我還是喜歡Ruby)。
以下我們來看看Array的例子:
var q [3]int = [3]int{1, 2, 3}
這樣的宣告代表了有三個整數的Array,不能增減,也不能放其他類別。所以實務上Slice比較常用(因為比較靈活)。假如宣告長度為三,但卻只有給兩個值,那麼第三個空位就會自動給初始值。
var r [3]int = [3]int{1, 2}
fmt.Println(r[2]) // "0"
長度可以省略宣告,由給值的長度決定,省略時使用...
:
q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"
前面有說過,Array是不能增減的,所以如果給值的數量超過當初宣告,編譯時會錯:
q := [3]int{1, 2, 3}
q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int
Slice跟Array有九成像,但是長度可以變動,不過一樣需要是相同類別。我們來看個例子:
months := [...]string{1: "January", /* ... */, 12: "December"}
如果沒有給索引的話,就會從0開始。這邊索引定義從1~12。如果要取特定元素,只要放入索引值,例如months[1]
會返回January,也可以取一個區段。
Q2 := months[4:7]
summer := months[6:9]
fmt.Println(Q2) // ["April" "May" "June"]
fmt.Println(summer) // ["June" "July" "August"]
雜湊是無順序的key與value組合,所有key都必須不同。有兩種宣告方式:
ages := make(map[string]int)
// or
ages := map[string]int{
"alice": 31,
"charlie": 34,
}
上面這種寫法等同於:
ages := make(map[string]int)
ages["alice"] = 31
ages["charlie"] = 34
可以使用內建的刪除方法delete
:
delete(ages, "alice")
除了預設的基本類型之外,go還有自定類型的設計。可以將商業邏輯與變數型態做更深的綁定。結構如下:
type 自訂類別名稱 基礎類別
這邊用攝氏華氏溫度轉換的例子,就會比較容易理解:
package tempconv
import "fmt"
type Celsius float64 // 攝氏温度
type Fahrenheit float64 // 華氏温度
const (
AbsoluteZeroC Celsius = -273.15 // 絕對零度
FreezingC Celsius = 0 // 結冰溫度
BoilingC Celsius = 100 // 沸點
)
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
攝氏與華氏溫度分別對應不同溫度單位,雖然底層都是float64
,但卻是不同的資料類型,不能混雜計算。這個設計相當不錯,我認為可以算是go優於ruby的設計的地方。
var c Celsius
var f Fahrenheit
fmt.Println(c == 0) // "true"
fmt.Println(f == 0) // "true"
fmt.Println(c == f) // compile error: type mismatch
我們可以看上面的例子更具體。雖然攝氏零度與華氏零度都等於零,但他們並不相等。自訂類型還可以綁定方法,許多的自訂類型都會覆寫string方法,類似Java裡面常常會覆寫toString一樣:
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
上面這個例子前面表示這是一個攝氏類型的String方法,會返回一個帶有攝氏符號°C
的字串,使用的範例如下:
c := FToC(212.0)
fmt.Println(c.String()) // "100°C"
Slice那邊,給定 index,是產生最大index+1個元素。
months := [...]string{2: "Jan", 4: "Feb"}
// 這三個會是空白的。
fmt.Println(months[0], months[1], months[3])
// 這個會跳錯誤 invalid array index 5 (out of bounds for 5-element array)
fmt.Println(months[5])
看起來go的slice應該是以array為基本。